/**************************************************************************
 *  Copyright (C) 2008 - 2010 by Simon Qian                               *
 *  SimonQian@SimonQian.com                                               *
 *                                                                        *
 *  Project:    Versaloon                                                 *
 *  File:       BDM.c                                                     *
 *  Author:     SimonQian                                                 *
 *  Versaion:   See changelog                                             *
 *  Purpose:    BDM interface implementation file                         *
 *  License:    See license                                               *
 *------------------------------------------------------------------------*
 *  Change Log:                                                           *
 *      YYYY-MM-DD:     What(by Who)                                      *
 *      2011-05-09:     created(by SimonQian)                             *
 **************************************************************************/

#include "vsf.h"
#include "core.h"

#define VSFHAL_USBD_EP_NUM					4
const uint8_t vsfhal_usbd_ep_num = VSFHAL_USBD_EP_NUM;
struct vsfhal_usbd_callback_t vsfhal_usbd_callback;

static uint16_t vsfhal_usbd_IN_epsize[VSFHAL_USBD_EP_NUM + 1];
static uint16_t vsfhal_usbd_OUT_epsize[VSFHAL_USBD_EP_NUM + 1];
static volatile bool vsfhal_setup_status_IN, vsfhal_status_out = false;

vsf_err_t vsfhal_usbd_init(int32_t int_priority)
{
	struct vsfhal_info_t *vsfhal_info;
	if (vsfhal_core_get_info(&vsfhal_info))
		return VSFERR_FAIL;

	memset(vsfhal_usbd_IN_epsize, 0, sizeof(vsfhal_usbd_IN_epsize));
	memset(vsfhal_usbd_OUT_epsize, 0, sizeof(vsfhal_usbd_OUT_epsize));

	// USBEN MUST BE disabled to configure the PHY clock
	RCC->APB1ENR &= ~RCC_APB1ENR_USBEN;
	RCC->CFGR &= ~RCC_CFGR_USBPRE;
	if (vsfhal_info->pll_freq_hz == 48000000){}
	else if (vsfhal_info->pll_freq_hz == 96000000)
		RCC->CFGR |= 1 << 22;
	else if (vsfhal_info->pll_freq_hz == 144000000)
		RCC->CFGR |= 2 << 22;
	else if (vsfhal_info->pll_freq_hz == 192000000)
		RCC->CFGR |= 3 << 22;
	else
		return VSFERR_FAIL;
	RCC->APB1ENR |= RCC_APB1ENR_USBEN;
//	RCC->APB1RSTR |= RCC_APB1RSTR_USBRST;
//	RCC->APB1RSTR &= ~RCC_APB1RSTR_USBRST;

	if (int_priority >= 0)
	{
		NVIC_SetPriority(USB_HP_CAN1_TX_IRQn, int_priority);
		NVIC_EnableIRQ(USB_HP_CAN1_TX_IRQn);
	}

	USB->rINT_STATE = USB_INT_STATE_RSTF | USB_INT_STATE_SUSPENDF |
			USB_INT_STATE_RESUMF | USB_INT_STATE_SOFF;
	USB->rINT_EN = USB_INT_EN_RSTIE | USB_INT_EN_SUSPENDIE | USB_INT_EN_RESUMIE |
			USB_INT_EN_EPINTIE;
	return VSFERR_NONE;
}

vsf_err_t vsfhal_usbd_fini(void)
{
	NVIC_DisableIRQ(USB_HP_CAN1_TX_IRQn);
	USB->rTOP = USB_TOP_RESET;
	USB->rINT_STATE = 0;
	RCC->APB1ENR &= ~RCC_APB1ENR_USBEN;
	return VSFERR_NONE;
}

vsf_err_t vsfhal_usbd_reset(void)
{
	USB->rTOP |= USB_TOP_RESET;
	USB->rTOP &= ~USB_TOP_RESET;
	USB->rEP_EN = 0;
	return VSFERR_NONE;
}

void USB_Istr(void);
vsf_err_t vsfhal_usbd_poll(void)
{
	USB_Istr();
	return VSFERR_NONE;
}

vsf_err_t vsfhal_usbd_connect(void)
{
	USB->rTOP |= USB_TOP_CONNECT | USB_TOP_ACTIVE;
	return VSFERR_NONE;
}

vsf_err_t vsfhal_usbd_disconnect(void)
{
	USB->rTOP &= ~(USB_TOP_CONNECT | USB_TOP_ACTIVE);
	return VSFERR_NONE;
}

vsf_err_t vsfhal_usbd_set_address(uint8_t address)
{
	// hardware will process address
//	USB->rADDR = address & USB_ADDR_ADDR;
	return VSFERR_NONE;
}

uint8_t vsfhal_usbd_get_address(void)
{
	return USB->rADDR & USB_ADDR_ADDR;
}

vsf_err_t vsfhal_usbd_wakeup(void)
{
	USB->rTOP |= USB_TOP_CONNECT;
	return VSFERR_NONE;
}

uint32_t vsfhal_usbd_get_frame_number(void)
{
	return 0;
}

vsf_err_t vsfhal_usbd_get_setup(uint8_t *buffer)
{
	for (int i = 0; i < 8; i++)
		buffer[i] = USB->rSETUP[i];
	vsfhal_setup_status_IN = (buffer[0] & 0x80) > 0;
	return VSFERR_NONE;
}

vsf_err_t vsfhal_usbd_prepare_buffer(void)
{
	return VSFERR_NONE;
}

vsf_err_t vsfhal_usbd_ep_reset(uint8_t idx)
{
	return VSFERR_NONE;
}

vsf_err_t vsfhal_usbd_ep_set_type(uint8_t idx, enum vsfhal_usbd_eptype_t type)
{
	uint32_t *ep_inten = !idx ? (uint32_t *)&USB->rEP0_INT_EN :
				(uint32_t *)(&USB->rEP1_INT_EN + idx - 1);
	USB->rEP_EN |= 1 << idx;
	*ep_inten = EPn_INT_EN_OUTACKIE | EPn_INT_EN_INACKIE;
	if (!idx)
		*ep_inten |= EPn_INT_EN_SETUPIE | EPn_INT_EN_ENDIE;
	USB->rEP_INT_EN |= 1 << idx;
	return VSFERR_NONE;
}

vsf_err_t vsfhal_usbd_ep_set_IN_dbuffer(uint8_t idx)
{
	return VSFERR_FAIL;
}

bool vsfhal_usbd_ep_is_IN_dbuffer(uint8_t idx)
{
	return false;
}

vsf_err_t vsfhal_usbd_ep_switch_IN_buffer(uint8_t idx)
{
	return VSFERR_FAIL;
}

vsf_err_t vsfhal_usbd_ep_set_IN_epsize(uint8_t idx, uint16_t epsize)
{
	if (epsize > 64)
		return VSFERR_FAIL;
	vsfhal_usbd_IN_epsize[idx] = epsize;
	return VSFERR_NONE;
}

uint16_t vsfhal_usbd_ep_get_IN_epsize(uint8_t idx)
{
	return vsfhal_usbd_IN_epsize[idx];
}

vsf_err_t vsfhal_usbd_ep_set_IN_stall(uint8_t idx)
{
	USB->rEP_HALT |= 1 << idx;
	return VSFERR_NONE;
}

vsf_err_t vsfhal_usbd_ep_clear_IN_stall(uint8_t idx)
{
	USB->rEP_HALT &= ~(1 << idx);
	return VSFERR_NONE;
}

bool vsfhal_usbd_ep_is_IN_stall(uint8_t idx)
{
	return USB->rEP_HALT & (1 << idx);
}

vsf_err_t vsfhal_usbd_ep_reset_IN_toggle(uint8_t idx)
{
	if (!idx) return VSFERR_NONE;
	USB->rTOG_CTRL1_4 &= ~(1 << ((idx - 1) << 1));
	USB->rTOG_CTRL1_4 |= 1 << ((idx - 1) + 1);
	USB->rTOG_CTRL1_4 &= ~(1 << ((idx - 1) + 1));
	return VSFERR_NONE;
}

vsf_err_t vsfhal_usbd_ep_toggle_IN_toggle(uint8_t idx)
{
	return VSFERR_NONE;
}

vsf_err_t vsfhal_usbd_ep_set_IN_count(uint8_t idx, uint16_t size)
{
	uint32_t *ep_ctrl = (uint32_t *)(&USB->rEP0_CTRL + idx);
	*ep_ctrl = EP0_CTRL_TRANEN | (size & EP0_CTRL_TRANCOUNT);
	return VSFERR_NONE;
}

vsf_err_t vsfhal_usbd_ep_write_IN_buffer(uint8_t idx, uint8_t *buffer, uint16_t size)
{
	uint32_t *ep_fifo = (uint32_t *)(&USB->rEP0_FIFO + idx);
	for (int i = 0; i < size; i++)
		*ep_fifo = buffer[i];
	return VSFERR_NONE;
}

vsf_err_t vsfhal_usbd_ep_set_OUT_dbuffer(uint8_t idx)
{
	return VSFERR_FAIL;
}

bool vsfhal_usbd_ep_is_OUT_dbuffer(uint8_t idx)
{
	return false;
}

vsf_err_t vsfhal_usbd_ep_switch_OUT_buffer(uint8_t idx)
{
	return VSFERR_FAIL;
}

vsf_err_t vsfhal_usbd_ep_set_OUT_epsize(uint8_t idx, uint16_t epsize)
{
	vsfhal_usbd_OUT_epsize[idx] = epsize;
	return VSFERR_NONE;
}

uint16_t vsfhal_usbd_ep_get_OUT_epsize(uint8_t idx)
{
	return vsfhal_usbd_OUT_epsize[idx];
}

vsf_err_t vsfhal_usbd_ep_set_OUT_stall(uint8_t idx)
{
	return vsfhal_usbd_ep_set_IN_stall(idx);
}

vsf_err_t vsfhal_usbd_ep_clear_OUT_stall(uint8_t idx)
{
	return vsfhal_usbd_ep_clear_IN_stall(idx);
}

bool vsfhal_usbd_ep_is_OUT_stall(uint8_t idx)
{
	return vsfhal_usbd_ep_is_IN_stall(idx);
}

vsf_err_t vsfhal_usbd_ep_reset_OUT_toggle(uint8_t idx)
{
	return vsfhal_usbd_ep_reset_IN_toggle(idx);
}

vsf_err_t vsfhal_usbd_ep_toggle_OUT_toggle(uint8_t idx)
{
	return vsfhal_usbd_ep_toggle_IN_toggle(idx);
}

uint16_t vsfhal_usbd_ep_get_OUT_count(uint8_t idx)
{
	if (!idx && vsfhal_status_out)
	{
		vsfhal_status_out = false;
		return 0;
	}
	else
	{
		uint32_t *ep_avail = (uint32_t *)(&USB->rEP0_AVIL + idx);
		return *ep_avail & EP0_AVIL_EPXAVIL;
	}
}

vsf_err_t vsfhal_usbd_ep_read_OUT_buffer(uint8_t idx, uint8_t *buffer, uint16_t size)
{
	if (size > 0)
	{
		uint32_t *ep_fifo = (uint32_t *)(&USB->rEP0_FIFO + idx);
		for (int i = 0; i < size; i++)
			buffer[i] = (uint8_t)*ep_fifo;
	}
	return VSFERR_NONE;
}

vsf_err_t vsfhal_usbd_ep_enable_OUT(uint8_t idx)
{
	if (!idx && vsfhal_setup_status_IN)
	{
		// todo: wait status
	}
	else
	{
		uint32_t *ep_inten = !idx ? (uint32_t *)&USB->rEP0_INT_EN :
				(uint32_t *)(&USB->rEP1_INT_EN + idx - 1);
		*ep_inten |= EPn_INT_EN_OUTACKIE;
	}
	return VSFERR_NONE;
}

static void vsfhal_usbd_cb(enum vsfhal_usbd_evt_t evt, uint32_t value)
{
	if (vsfhal_usbd_callback.on_event != NULL)
		vsfhal_usbd_callback.on_event(vsfhal_usbd_callback.param, evt, value);
}

void USB_Istr(void)
{
	uint32_t wIstr = USB->rINT_STATE & USB->rINT_EN;
	USB->rINT_STATE = wIstr;

	if (wIstr & USB_INT_STATE_RSTF)
	{
		vsfhal_status_out = false;
		vsfhal_usbd_cb(VSFHAL_USBD_ON_RESET, 0);
	}
	if (wIstr & USB_INT_STATE_RESUMF)
		vsfhal_usbd_cb(VSFHAL_USBD_ON_RESUME, 0);
	if (wIstr & USB_INT_STATE_SUSPENDF)
		vsfhal_usbd_cb(VSFHAL_USBD_ON_SUSPEND, 0);
	if (wIstr & USB_INT_STATE_SOFF)
		vsfhal_usbd_cb(VSFHAL_USBD_ON_SOF, 0);
	if (wIstr & USB_INT_STATE_EPINTF)
	{
		uint16_t wEPVal;
		wIstr = USB->rEP_INT_STATE;

		for (int i = 0; i < VSFHAL_USBD_EP_NUM + 1; i++)
		{
			if (wIstr & (1 << i))
			{
				uint32_t *ep_ints, *ep_inten;

				if (!i)
				{
					ep_ints = (uint32_t *)&USB->rEP0_INT_STATE;
					ep_inten = (uint32_t *)&USB->rEP0_INT_EN;
				}
				else
				{
					ep_ints = (uint32_t *)(&USB->rEP1_INT_STATE + i - 1);
					ep_inten = (uint32_t *)(&USB->rEP1_INT_EN + i - 1);
				}

				wEPVal = *ep_ints & *ep_inten;
				*ep_ints = wEPVal;

				if ((wEPVal & EPn_INT_STATE_INACK) != 0)
					vsfhal_usbd_cb(VSFHAL_USBD_ON_IN, i);
				if (!i && (wEPVal & EPn_INT_STATE_END))
				{
					// no need to process on_in in END(STATUS) interrupt,
					//	becuase already processed in INACK interrupt
					if (vsfhal_setup_status_IN)
					{
						vsfhal_status_out = true;
						vsfhal_usbd_cb(VSFHAL_USBD_ON_OUT, 0);
					}
				}
				if (!i && (wEPVal & EPn_INT_STATE_SETUP))
					vsfhal_usbd_cb(VSFHAL_USBD_ON_SETUP, 0);
				if ((wEPVal & EPn_INT_STATE_OUTACK) != 0)
					vsfhal_usbd_cb(VSFHAL_USBD_ON_OUT, i);
			}
		}
	}
}

ROOTFUNC void USB_HP_CAN1_TX_IRQHandler(void)
{
	USB_Istr();
}

ROOTFUNC void USBWakeUp_IRQHandler(void)
{
}
